package com.hoe.tsb.fraft.serialize;

import com.hoe.tsb.fraft.event.msg.FRaftMessage;
import com.hoe.tsb.fraft.exception.FRaftException;
import com.xiaoleilu.hutool.crypto.digest.DigestUtil;
import com.xiaoleilu.hutool.lang.Assert;

import java.nio.charset.StandardCharsets;
import java.util.Random;

/**
 * <p>
 * 序列化器
 * </p>
 *
 * @author hoe
 * @since 2022/11/21
 */
public interface Serializer {

    /**
     * 序列化<br/>
     * 第1字节为消息类型<br/>
     * 后续字节为动态消息体（可能为空）
     * @param t 待序列化的对象
     * @return 序列化之后的字节数组
     */
    byte[] serialize(FRaftMessage t);

    /**
     * 序列化并且计算摘要<br/>
     * 先序列化对象生成字节数组，然后再计算摘要，对序列化的字节数组扩容16byte并将前2~17共计16byte作为摘要<br/>
     * 目前消息摘要和序列化器耦合在一起的，后续可能考虑两者的解耦
     * @param t 待序列化的对象
     * @return 序列化之后的字节数组
     */
    default byte[] serializeWithSign(FRaftMessage t) {
        byte[] serialize = serialize(t);
        Assert.notNull(serialize, "序列化的字节数组不能为空");
        byte[] signByte = signWithTsAndRand(serialize);
        byte[] originBody = new byte[serialize.length + 16];
        //消息类型
        originBody[0] = serialize[0];
        //消息摘要
        System.arraycopy(signByte, 0, originBody, 1, 16);
        //消息体
        System.arraycopy(serialize, 1, originBody, 17, serialize.length - 1);
        return originBody;
    }

    /**
     * 抽取摘要并序列化<br/>
     * @param buf 待反序列化的字节数组
     * @return 序列化之后的字节数组
     */
    default FRaftMessage deserializeWithSign(byte[] buf) {
        //sign会有专门的方法进行反序列化
        byte[] bodyBytes = new byte[buf.length - 16];
        //消息体
        bodyBytes[0] = buf[0];
        System.arraycopy(buf, 17, bodyBytes, 1, buf.length - 17);
        return deserialize(bodyBytes);
    }

    /**
     * 对原始数据追加时间戳和随机数后进行摘要计算<br/>
     * 避免同样的数据计算出的摘要信息一样，导致重复消费判断有误的问题
     * @param data 待计算签名的原始数据
     * @return 追加时间戳和随机数后计算出的摘要
     */
    default byte[] signWithTsAndRand(byte[] data) {
        long l = System.currentTimeMillis();
        //TODO 可以专门定义一个随机数生成类
        long l1 = new Random().nextLong();
        byte[] tsAndRand = (l + "" + l1).getBytes(StandardCharsets.UTF_8);
        byte[] newBytes = new byte[data.length + tsAndRand.length];
        System.arraycopy(data, 0, newBytes, 0, data.length);
        System.arraycopy(tsAndRand, 0, newBytes, data.length, tsAndRand.length);
        return DigestUtil.md5(newBytes);
    }

    /**
     * 反序列化
     * @param buf 待反序列化的数据
     * @return 序列化之后的对象
     */
    FRaftMessage deserialize(byte[] buf);

}
